package org.ecpkn.PAGridSim.core.Simulator;

import gridsim.GridSim;
import gridsim.GridSimTags;
import gridsim.Gridlet;
import gridsim.GridletList;
import gridsim.ResourceCharacteristics;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.ecpkn.PAGridSim.core.PAEntityFactory;
import org.ecpkn.PAGridSim.core.configuration.Configuration;
import org.ecpkn.PAGridSim.core.offlineOptimization.ChangingWorkloadOptimizationAlgo;
import org.ecpkn.PAGridSim.core.offlineOptimization.OptimizedResultEntry;
import org.ecpkn.PAGridSim.core.offlineOptimization.workloadBalance.LoadBalanceStrategy;
import org.ecpkn.PAGridSim.core.tasksCreation.Job;
import org.ecpkn.PAGridSim.core.tasksCreation.PeriodTaskCreator;
import org.ecpkn.PAGridSim.core.tasksCreation.RandomTaskCreator;
import org.ecpkn.PAGridSim.core.tasksCreation.Task;

/**
 * A GridSim entity that will be sent to the resource.
 * 
 * @author Lin Zhou
 * 
 *         Module:gridSimEntity
 * 
 */
public class Workload extends GridSim {

	private static final Logger logger = LogManager.getLogger(Workload.class);

	private int totalResource_ = 1;// we assume that there is only one resource
	// time constant values
	private final int SEC = 1; // 1 second
	// GridSimTags take negative nombers 0-299,so we take -300;

	private Integer ID_;
	private GridletList list_; // a list containing new Gridlets
	private GridletList receiveList_; // a list containing completed Gridlets
	protected ArrayList<Task> tasks;
	private String optimizationName_;

	// change utilization and frequency
	private HashMap<Double, OptimizedResultEntry> optimizedUF_;

	/**
	 * 
	 * @param name
	 *            Workload
	 * @param bandwidth
	 * @param tasks
	 * @throws Exception
	 */
	public Workload(String name, String optimizationName, double bandwidth)
			throws Exception {
		super(name, bandwidth);
		this.receiveList_ = new GridletList();
		// Gets an ID for this entity
		this.ID_ = new Integer(getEntityId(name));
		this.optimizationName_ = optimizationName;
		PeriodTaskCreator tc = PAEntityFactory
				.createCustomizedTaskCreator(Configuration.getInstance()
						.getParameterString(Configuration.Const.TASK_CREATOR));
		tasks = tc.creatTasks();

		// optimizedUF_ should be given in the following method
		paOptimization();

		list_ = createGridletList();
		logger.debug("Creating a grid user entity with name = "
				+ super.get_name() + ", and id = " + this.ID_);

	}

	public HashMap<Double, OptimizedResultEntry> getOptimizedUF_() {
		return optimizedUF_;
	}

	private void workloadBalance(double timePoint) throws Exception {
		optimizedUF_.get(timePoint).setModelMachineList(
				LoadBalanceStrategy.schedule(timePoint,
						optimizedUF_.get(timePoint).getOptimizedNodesNumber_(),
						(int) optimizedUF_.get(timePoint)
								.getOptimizedFrequency_(),
						ChangingWorkloadOptimizationAlgo.changingTasks_
								.get(timePoint)));
		// throw new Exception(
		// "Workload.workloadBalance:this methode should be override to balance workload");
	}

	/**
	 * 
	 * @throws Exception
	 */
	public void paOptimization() throws Exception {
		ChangingWorkloadOptimizationAlgo o = PAEntityFactory
				.createCustomizedOptimizationMethod(optimizationName_, tasks);
		optimizedUF_ = o.optimize();
	};

	/**
	 * 
	 * @param jobs
	 *            Job
	 * @return GridletList
	 */
	private GridletList createGridletList() {

		ArrayList<Job> jobs = RandomTaskCreator
				.createSubmitTimeSortedJobs(tasks);
		for (int i = 0; i < jobs.size(); i++) {
			jobs.get(i).setUserID(ID_);
		}
		// Creates a container to store Gridlets
		GridletList list = new GridletList();
		// add the jobs(Gridlet) into a list
		for (int i = 0; i < jobs.size(); i++) {
			list.add(jobs.get(i));
		}

		return list;
	}

	/**
	 * Gets the list of Gridlets
	 * 
	 * @return a list of Gridlets
	 */

	public GridletList getReceiveList() {
		return this.receiveList_;
	}

	public void body() {
		int resourceID[] = new int[this.totalResource_];
		double resourceCost[] = new double[this.totalResource_];
		String resourceName[] = new String[this.totalResource_];
		LinkedList<?> resList;
		ResourceCharacteristics resChar;
		// waiting to get list of resources. Since GridSim package uses
		// multi-threaded environment, your request might arrive earlier
		// before one or more grid resource entities manage to register
		// themselves to GridInformationService (GIS) entity.
		// Therefore, it's better to wait in the first place
		while (true) {
			// need to pause for a while to wait GridResources finish
			// registering to GIS
			super.gridSimHold(0.1 * SEC); // wait for 0.1 seconds

			// get Resources IDs
			resList = getGridResourceList();
			if (resList.size() > 0) {
				break;
			} else {
				logger.debug(super.get_name()
						+ ": Waiting to get list of resources ...");
			}
		}
		// list of resource names
		ArrayList<String> resNameList = new ArrayList<String>();

		String name; // resource name

		// a loop that gets a list of resources

		for (int i = 0; i < this.totalResource_; i++) {
			// Resource list contains list of resource IDs not grid resource
			// objects.
			resourceID[i] = ((Integer) resList.get(i)).intValue();
			// Requests to resource entity to send its characteristics
			send(resourceID[i], GridSimTags.SCHEDULE_NOW,
					GridSimTags.RESOURCE_CHARACTERISTICS, this.ID_);

			// gets the name of a resource
			name = GridSim.getEntityName(resourceID[i]);
			resNameList.add(name);
			// waiting to get a resource characteristics
			resChar = (ResourceCharacteristics) receiveEventObject();
			resourceName[i] = resChar.getResourceName();
			resourceCost[i] = resChar.getCostPerSec();

			logger.debug(super.get_name()
					+ ":Received ResourceCharacteristics from "
					+ resourceName[i] + ", with id = " + resourceID[i]);
		}
		// //CHANGE nodesNumber,utilization and frequency to adapt the workload

		String mode = Configuration.getInstance().getParameterString(
				Configuration.Const.TASKS_ARRIVING_MODE);

		if (Configuration.Const.ARRIVING_MODE_START_TOGETHER.equals(mode)) {

			Double key = ChangingWorkloadOptimizationAlgo.sortedTimePoints
					.first();
			try {
				workloadBalance(key);
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			send(resourceID[0], key - GridSim.clock(),
					Configuration.Const.CHANGE_NODE_FREQUENCY,
					optimizedUF_.get(key));
		} else {
			for (Double key : ChangingWorkloadOptimizationAlgo.sortedTimePoints) {
				try {
					workloadBalance(key);
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				send(resourceID[0], key - GridSim.clock(),
						Configuration.Const.CHANGE_NODE_FREQUENCY,
						optimizedUF_.get(key));
			}
		}
		// ///////////////////// SUBMITS Gridlets/////

		Job job = null;

		// a loop to get one Gridlet at one time and sends it to a random grid
		// resource entity. Then waits for a reply
		int id = 0;

		double sendDelay = 0;

		for (int i = 0; i < this.list_.size(); i++) {
			job = (Job) this.list_.get(i);
			sendDelay = job.getArriveTime_() - GridSim.clock();
			if (sendDelay < 0) {
				sendDelay = 0;
			}
			// Sends one Gridlet to a grid resource specified in "resourceID"
			gridletSubmit(job, resourceID[id], sendDelay, false);
		}
		Gridlet gridlet = null;

		// ////////////////////////////////////////////////
		// RECEIVES Gridlets
		// hold time should be long enough to prevent the simulation from
		// stopping too early
		super.gridSimHold(1000000 * SEC);
		// A loop to receive all the Gridlets back
		for (int i = 0; i < this.list_.size(); i++) {
			// waiting to receive a Gridlet back from resource entity
			gridlet = (Gridlet) super.receiveEventObject();
			// stores the received Gridlet into a new GridletList object
			this.receiveList_.add(gridlet);
		}		
		shutdownGridStatisticsEntity();
		terminateIOEntities();
		shutdownUserEntity();
	}

}
